package controller

import (
	"compress/flate"
	"embed"
	"gitee.com/yysf-xin/dproxy/controller/proxyc"
	"gitee.com/yysf-xin/dproxy/controller/qrcode"
	"gitee.com/yysf-xin/dproxy/controller/spider"
	"gitee.com/yysf-xin/dproxy/controller/user"
	"gitee.com/yysf-xin/dproxy/pkg/app"
	"gitee.com/yysf-xin/dproxy/pkg/corn"
	"gitee.com/yysf-xin/dproxy/pkg/db"
	_ "gitee.com/yysf-xin/dproxy/pkg/ddns"
	"gitee.com/yysf-xin/dproxy/pkg/navcat"
	"gitee.com/yysf-xin/dproxy/pkg/plugin"
	"gitee.com/yysf-xin/dproxy/pkg/rest"
	"gitee.com/yysf-xin/dproxy/pkg/tools"
	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/go-chi/jwtauth/v5"
	"net/http"
	"os"
	"reflect"
	"strings"
	"time"
)

type RouterConfig struct {
	Cfg            *app.ConfigFile
	Ssh            *proxyc.SSHControl
	LocalControl   *proxyc.LocalControl
	RouteControl   *proxyc.RouteControl
	ProxyControl   *proxyc.ProxyControl
	Tunnel         *proxyc.TunnelControl
	TurnControl    *proxyc.TurnControl
	Socket5Control *proxyc.Socket5Control
	ServerControl  *proxyc.ServerControl
	UserControl    *user.Control
	CornControl    *corn.Control
	DB             *db.Client
	JWTAuth        *jwtauth.JWTAuth
	Spider         *spider.Spider
	ToolsRoute     *ToolsRoute
	V2flay         *spider.V2flay
	Server         chi.Router  `wire:"-"`
	apis           []*rest.Api `wire:"-"`
}

type ToolsRoute struct {
	Qrcode *qrcode.QrGenerator
}

func (r *RouterConfig) Match(method, path string) bool {
	var ctx chi.Context
	ctx.Reset()
	if r.Server.Match(&ctx, method, path) {
		return true
	}
	return false
}

func InitRoute(r interface{}, ru chi.Router) {
	var values = reflect.ValueOf(r).Elem()
	for i := 0; i < values.NumField(); i++ {
		f := values.Field(i)
		if rst, ok := f.Interface().(app.InitRouter); ok {
			rst.InitRoute(ru)
		}
	}
}

func (r *RouterConfig) initRestApi(ru chi.Router) {
	var values = reflect.ValueOf(r.DB.Models)
	for i := 0; i < values.NumField(); i++ {
		f := values.Field(i)
		if rst, ok := f.Interface().(app.Modeler); ok {
			r.apis = append(r.apis, rest.NewRestApi(rst))
		}
	}
	for i := range r.apis {
		r.apis[i].Db = r.DB.Db
		r.apis[i].InitRoute(ru)
	}
}

func (r *RouterConfig) initControl(route func(route chi.Router), model app.Modeler) {
	for i := range r.apis {
		if r.apis[i].Model.TableName() == model.TableName() {
			route(r.apis[i].Router)
			break
		}
	}
}

//go:embed static
var Assets embed.FS

func (r *RouterConfig) Init() {
	chi.RegisterMethod("MKCOL")
	chi.RegisterMethod("COPY")
	chi.RegisterMethod("MOVE")
	chi.RegisterMethod("LOCK")
	chi.RegisterMethod("UNLOCK")
	chi.RegisterMethod("PROPFIND")
	chi.RegisterMethod("PROPPATCH")

	verifier := jwtauth.Verifier(r.JWTAuth)
	r.Server = chi.NewRouter()

	//r.Server.Mount("/debug", middleware.Profiler())
	r.Server.Route("/api/", func(api chi.Router) {

		// public
		r.UserControl.InitLogin(api)
		// private
		api.Group(func(pr chi.Router) {
			pr.Use(verifier)
			pr.Use(jwtauth.Authenticator)
			pr.Route("/user/", r.UserControl.InitUserRoute)
			pr.Route("/model/", r.initRestApi)
			//api.Route("/socket5",r.Socket5Control.InitRoute)
			pr.Route("/server", r.ServerControl.InitRoute)

			initPlugins(api, plugin.HttpPrivate)
		})
		// turn
		api.Route("/turn", func(ru chi.Router) {
			r.TurnControl.InitTurnRoute(ru)
			ru.Group(func(rur chi.Router) {
				rur.Use(verifier)
				rur.Use(jwtauth.Authenticator)
				r.TurnControl.InitRoute(rur)
			})
		})

		api.Route("/tools", func(ru chi.Router) {
			r.ToolsRoute.Qrcode.InitRoute(ru)
		})
		navcat.Init(api)

		api.Get("/dict", app.ServeDict)
		api.Route("/clash", r.V2flay.PubRoute)

		initPlugins(api, plugin.HttpPublic)
	})

	// 临时修复接口路径问题
	r.Server.HandleFunc("/server/fly/checkFile", func(w http.ResponseWriter, rr *http.Request) {
		rr.URL.Path = "/api" + rr.URL.Path
		r.Server.ServeHTTP(w, rr)
	})

	r.initControl(r.Ssh.InitRoute, r.DB.Models.SshServer)
	r.initControl(r.Tunnel.InitRoute, r.DB.Models.Tunnel)
	r.initControl(r.LocalControl.InitRoute, r.DB.Models.LocalHttp)
	r.initControl(r.RouteControl.InitForward, r.DB.Models.TcpForward)
	r.initControl(r.RouteControl.InitRule, r.DB.Models.RouteRule)
	r.initControl(r.RouteControl.InitProxyPassRoute, r.DB.Models.ProxyPass)
	r.initControl(r.ProxyControl.InitRoute, r.DB.Models.Proxy)
	r.initControl(r.TurnControl.InitClient, r.DB.Models.TurnClient)
	r.initControl(r.TurnControl.InitTurnGroup, r.DB.Models.TurnGroup)
	r.initControl(r.CornControl.InitRoute, r.DB.Models.CornConfig)

	// static
	r.Server.Group(func(r chi.Router) {
		r.Use(middleware.Compress(flate.BestSpeed))
		fi, _ := os.Stat(os.Args[0])
		var t time.Time
		if fi != nil && !fi.ModTime().IsZero() {
			t = fi.ModTime()
		} else {
			t = time.Now()
		}

		f := http.FileServer(&StaticFSWrapper{
			FileSystem:   http.FS(Assets),
			FixedModTime: t,
		})
		r.Handle("/static/*", f)
		r.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			r.URL.Path += "static/"
			f.ServeHTTP(w, r)
		}))
	})

	r.Server.Handle("/*", r.LocalControl)

	if r.Cfg.Log.Router {
		walkFunc := func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
			route = strings.Replace(route, "/*/", "/", -1)
			tools.Log.Println(method, route)
			return nil
		}
		if err := chi.Walk(r.Server, walkFunc); err != nil {
			tools.Log.Println("Logging err: ", err)
		}
	}

}

type StaticFSWrapper struct {
	http.FileSystem
	FixedModTime time.Time
}

func (f *StaticFSWrapper) Open(name string) (http.File, error) {
	file, err := f.FileSystem.Open(name)

	return &StaticFileWrapper{File: file, fixedModTime: f.FixedModTime}, err
}

type StaticFileWrapper struct {
	http.File
	fixedModTime time.Time
}

func (f *StaticFileWrapper) Stat() (os.FileInfo, error) {

	fileInfo, err := f.File.Stat()

	return &StaticFileInfoWrapper{FileInfo: fileInfo, fixedModTime: f.fixedModTime}, err
}

type StaticFileInfoWrapper struct {
	os.FileInfo
	fixedModTime time.Time
}

func (f *StaticFileInfoWrapper) ModTime() time.Time {
	return f.fixedModTime
}

func initPlugins(p chi.Router, pl map[string][]plugin.Http) {
	for k, v := range pl {
		if k == "" {
			for _, vl := range v {
				vl(p)
			}
		} else {
			p.Route(k, func(r chi.Router) {
				for _, vl := range v {
					vl(r)
				}
			})
		}
	}
}
